/*
 * Copyright 2012 Gregor Schauer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.schauer.gregor.vienna.convert.vienna.base;

import at.schauer.gregor.vienna.context.ViennaContext;
import at.schauer.gregor.vienna.context.ViennaContextAware;
import at.schauer.gregor.vienna.exception.ViennaException;
import at.schauer.gregor.vienna.serializer.Serializer;
import at.schauer.gregor.vienna.util.ViennaUtils;
import org.apache.commons.lang3.ClassUtils;
import org.springframework.beans.BeanUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author Gregor Schauer
 */
public abstract class AbstractContainerConverter<C, E> implements SeparatorConverter<C>, ViennaContextAware {
	protected static final Pattern INDICES_PATTERN = Pattern.compile("\\[[\\d,]*\\].{2,}");
	private static final Pattern SEPARATOR_PATTERN = Pattern.compile(",");
	public static final String[] EMPTY = new String[0];
	protected String containerBegin;
	protected String containerEnd;
	protected ViennaContext ctx;

	public AbstractContainerConverter(String containerBegin, String containerEnd) {
		this.containerBegin = containerBegin;
		this.containerEnd = containerEnd;
	}

	@Override
	public C sourceToTarget(String source, Class<? extends C> targetClass) {
		source = stripContainerSequences(source);
		String[] split = split(source, getSeparator());
		C instance = createInstance(targetClass, split.length);
		for (int i = 0; i < split.length; i++) {
			add(instance, i, this.<E>deserialize(split[i]));
		}
		return instance;
	}

	protected abstract void add(C instance, int i, E elem);

	@SuppressWarnings("unchecked")
	protected C createInstance(Class<? extends C> targetClass, int size) {
		Serializer<C> serializer = (Serializer<C>) ctx.getSerializer(targetClass);
		if (serializer != null) {
			return serializer.load(new HashMap<String, Object>(), targetClass);
		} else {
			return BeanUtils.instantiateClass(targetClass);
		}
	}

	@SuppressWarnings("unchecked")
	protected <E> E deserialize(String str) {
		try {
			String type = ViennaUtils.ConverterUtils.typeFromString(str);
			String value = ViennaUtils.ConverterUtils.valueFromString(str);
			Class<E> clazz = (Class<E>) ClassUtils.getClass(type);
			return (E) ViennaUtils.DeserializationUtils.load(ctx, clazz, value);
		} catch (ClassNotFoundException e) {
			throw new ViennaException(e);
		}
	}

	protected final String[] split(String str, String separator) {
		if (!INDICES_PATTERN.matcher(str).matches()) {
			str = stripContainerSequences(str);
			return str.isEmpty() ? EMPTY : str.split(separator);
		}
		String[] strPos = SEPARATOR_PATTERN.split(str.substring(1, str.indexOf(']')));
		Integer[] sepPositions = new Integer[strPos.length];
		for (int i = 0; i < strPos.length; i++) {
			sepPositions[i] = Integer.parseInt(strPos[i]);
		}

		String[] elements = new String[sepPositions.length];
		str = str.substring(str.indexOf(']') + 1);
		if (str.length() < 3) {
			return elements;
		}

		for (int i = 0, lastPos = 1; i < sepPositions.length; i++) {
			Integer sepPos = sepPositions[i];
			elements[i] = str.substring(lastPos, lastPos + sepPos);
			lastPos += sepPos + getSeparator().length();
		}
		return elements;
	}

	@Override
	public String targetToSource(C target) {
		StringBuilder builder = new StringBuilder();
		appendLeadingSequence(builder);

		boolean required = false;
		List<Integer> separatorPositions = new ArrayList<Integer>();
		for (Iterator<E> iterator = getElements(target); iterator.hasNext(); ) {
			E elem = iterator.next();
			Object identifier = ViennaUtils.SerializationUtils.save(ctx, elem);
			String str = org.springframework.util.ClassUtils.getQualifiedName(elem.getClass()) + Serializer.CLASS_OBJECT_SEPARATOR + identifier;
			builder.append(str).append(getSeparator());

			separatorPositions.add(str.length());
			required = required || requireIndices(str);
		}

		if (separatorPositions.size() > 1 && required) {
			builder.insert(0, separatorPositions.toString().replace(" ", ""));
		}
		return appendTrailingSequence(builder).toString();
	}

	protected boolean requireIndices(String str) {
		return str.contains(getSeparator());
	}

	protected abstract Iterator<E> getElements(C target);

	protected StringBuilder appendLeadingSequence(StringBuilder builder) {
		return builder.append(containerBegin);
	}

	protected final StringBuilder appendTrailingSequence(StringBuilder builder) {
		int pos = builder.lastIndexOf(getSeparator());
		if (pos == builder.length() - getSeparator().length()) {
			builder.delete(pos, builder.length());
		}
		return builder.append(containerEnd);
	}

	protected final String stripContainerSequences(String str) {
		return str.startsWith(containerBegin) ? str.substring(containerBegin.length(), str.length() - containerEnd.length()) : str;
	}

	@Override
	public final String getSeparator() {
		return ",";
	}

	@Override
	public void setViennaContext(ViennaContext viennaContext) {
		this.ctx = viennaContext;
	}
}
